/*-------------------------------------------------------------------------
 *
 * amcmds.c
 *	  操作访问方法的SQL命令的例程。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, 加利福尼亚大学董事会
 *
 *
 * IDENTIFICATION
 *	  src/backend/commands/amcmds.c
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/htup_details.h"
#include "access/table.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"


static Oid	fc_lookup_am_handler_func(List *fc_handler_name, char fc_amtype);
static const char *fc_get_am_type_string(char fc_amtype);


/*
 * CreateAccessMethod
 *		注册一个新的访问方法。
 */
ObjectAddress CreateAccessMethod(CreateAmStmt *fc_stmt)
{
	Relation	fc_rel;
	ObjectAddress fc_myself;
	ObjectAddress fc_referenced;
	Oid			fc_amoid;
	Oid			fc_amhandler;
	bool		fc_nulls[Natts_pg_am];
	Datum		fc_values[Natts_pg_am];
	HeapTuple	fc_tup;

	fc_rel = table_open(AccessMethodRelationId, RowExclusiveLock);

	/* 必须是超级用户 */
	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied to create access method \"%s\"",
						fc_stmt->amname),
				 errhint("Must be superuser to create an access method.")));

	/* 检查名称是否已使用 */
	fc_amoid = GetSysCacheOid1(AMNAME, Anum_pg_am_oid,
							CStringGetDatum(fc_stmt->amname));
	if (OidIsValid(fc_amoid))
	{
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("access method \"%s\" already exists",
						fc_stmt->amname)));
	}

	/*
	 * 获取处理函数 oid，同时验证 AM 类型。
	 */
	fc_amhandler = fc_lookup_am_handler_func(fc_stmt->handler_name, fc_stmt->amtype);

	/*
	 * 将元组插入 pg_am。
	 */
	memset(fc_values, 0, sizeof(fc_values));
	memset(fc_nulls, false, sizeof(fc_nulls));

	fc_amoid = GetNewOidWithIndex(fc_rel, AmOidIndexId, Anum_pg_am_oid);
	fc_values[Anum_pg_am_oid - 1] = ObjectIdGetDatum(fc_amoid);
	fc_values[Anum_pg_am_amname - 1] =
		DirectFunctionCall1(namein, CStringGetDatum(fc_stmt->amname));
	fc_values[Anum_pg_am_amhandler - 1] = ObjectIdGetDatum(fc_amhandler);
	fc_values[Anum_pg_am_amtype - 1] = CharGetDatum(fc_stmt->amtype);

	fc_tup = heap_form_tuple(RelationGetDescr(fc_rel), fc_values, fc_nulls);

	CatalogTupleInsert(fc_rel, fc_tup);
	heap_freetuple(fc_tup);

	fc_myself.classId = AccessMethodRelationId;
	fc_myself.objectId = fc_amoid;
	fc_myself.objectSubId = 0;

	/* 记录对处理函数的依赖 */
	fc_referenced.classId = ProcedureRelationId;
	fc_referenced.objectId = fc_amhandler;
	fc_referenced.objectSubId = 0;

	recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_NORMAL);

	recordDependencyOnCurrentExtension(&fc_myself, false);

	InvokeObjectPostCreateHook(AccessMethodRelationId, fc_amoid, 0);

	table_close(fc_rel, RowExclusiveLock);

	return fc_myself;
}

/*
 * get_am_type_oid
 *		各种 get_am_*_oid 变体的工作函数
 *
 * 如果 missing_ok 为 false，当找不到访问方法时抛出错误。如果
 * 为 true，则返回 InvalidOid。
 *
 * 如果 amtype 不为 '\0'，则如果找到的 AM 不是给定类型，将抛出错误。
 */
static Oid fc_get_am_type_oid(const char *fc_amname, char fc_amtype, bool fc_missing_ok)
{
	HeapTuple	fc_tup;
	Oid			fc_oid = InvalidOid;

	fc_tup = SearchSysCache1(AMNAME, CStringGetDatum(fc_amname));
	if (HeapTupleIsValid(fc_tup))
	{
		Form_pg_am	fc_amform = (Form_pg_am) GETSTRUCT(fc_tup);

		if (fc_amtype != '\0' &&
			fc_amform->amtype != fc_amtype)
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("access method \"%s\" is not of type %s",
							NameStr(fc_amform->amname),
							fc_get_am_type_string(fc_amtype))));

		fc_oid = fc_amform->oid;
		ReleaseSysCache(fc_tup);
	}

	if (!OidIsValid(fc_oid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("access method \"%s\" does not exist", fc_amname)));
	return fc_oid;
}

/*
 * get_index_am_oid - 给定一个访问方法名称，查找其 OID
 *		并验证其对应于一个索引 AM。
 */
Oid get_index_am_oid(const char *fc_amname, bool fc_missing_ok)
{
	return fc_get_am_type_oid(fc_amname, AMTYPE_INDEX, fc_missing_ok);
}

/*
 * get_table_am_oid - 给定一个访问方法名称，查找其 OID
 *		并验证其对应于一个表 AM。
 */
Oid get_table_am_oid(const char *fc_amname, bool fc_missing_ok)
{
	return fc_get_am_type_oid(fc_amname, AMTYPE_TABLE, fc_missing_ok);
}

/*
 * get_am_oid - 给定一个访问方法名称，查找其 OID。
 *		不检查类型。
 */
Oid get_am_oid(const char *fc_amname, bool fc_missing_ok)
{
	return fc_get_am_type_oid(fc_amname, '\0', fc_missing_ok);
}

/*
 * get_am_name - 给定一个访问方法 OID，查找其名称。
 */
char * get_am_name(Oid fc_amOid)
{
	HeapTuple	fc_tup;
	char	   *fc_result = NULL;

	fc_tup = SearchSysCache1(AMOID, ObjectIdGetDatum(fc_amOid));
	if (HeapTupleIsValid(fc_tup))
	{
		Form_pg_am	fc_amform = (Form_pg_am) GETSTRUCT(fc_tup);

		fc_result = pstrdup(NameStr(fc_amform->amname));
		ReleaseSysCache(fc_tup);
	}
	return fc_result;
}

/*
 * 将单字符访问方法类型转换为字符串以进行错误报告。
 */
static const char * fc_get_am_type_string(char fc_amtype)
{
	switch (fc_amtype)
	{
		case AMTYPE_INDEX:
			return "INDEX";
		case AMTYPE_TABLE:
			return "TABLE";
		default:
			/* 不应该发生 */
			elog(ERROR, "invalid access method type '%c'", fc_amtype);
			return NULL;		/* 保持编译器安静 */
	}
}

/*
 * 将处理函数名称转换为 Oid。如果函数的返回类型与
 * 给定的 AM 类型不匹配，将抛出错误。
 *
 * 该函数要么返回有效的函数 Oid，要么抛出错误。
 */
static Oid fc_lookup_am_handler_func(List *fc_handler_name, char fc_amtype)
{
	Oid			fc_handlerOid;
	Oid			fc_funcargtypes[1] = {INTERNALOID};
	Oid			fc_expectedType = InvalidOid;

	if (fc_handler_name == NIL)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("handler function is not specified")));

	/* handlers 有一个类型为 internal 的参数 */
	fc_handlerOid = LookupFuncName(fc_handler_name, 1, fc_funcargtypes, false);

	/* 检查处理函数是否具有正确的返回类型 */
	switch (fc_amtype)
	{
		case AMTYPE_INDEX:
			fc_expectedType = INDEX_AM_HANDLEROID;
			break;
		case AMTYPE_TABLE:
			fc_expectedType = TABLE_AM_HANDLEROID;
			break;
		default:
			elog(ERROR, "unrecognized access method type \"%c\"", fc_amtype);
	}

	if (get_func_rettype(fc_handlerOid) != fc_expectedType)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("function %s must return type %s",
						get_func_name(fc_handlerOid),
						format_type_extended(fc_expectedType, -1, 0))));

	return fc_handlerOid;
}
